#import os, sys

import pygame
if not pygame.font: print 'Warning, PyGame fonts not imported. They will be disabled.'
if not pygame.mixer: print 'Warning, PyGame sounds not imported. They will be disabled.'

from OpenGL.GL import *
from OpenGL.GLU import *

from Box2D2 import *

from World import *
from Renderer import *
from Input import *
from GUIDC import *
from Timing import *
from Debugging import *
from Rules import *

from PhysicsWorld import PhysicsWorld

import xml.dom.minidom as xmlHandler


class Error(Exception):
    pass
class EngineError(Error):
    pass




class Engine(object):

    def __init__(self):

        self.tickRate = 60

        # Event handling setup
        self.engine_event_rules = RuleSet()
        self.keyboard_bindings = {}
        self.mouse_bindings = {}
        self.event_handlers = {}

        # Default window setup
        self.windowWidth = 1000
        self.windowHeight = 700
        self.fullscreen = False
        self.forceResolution = False
        self.renderTime = 1.0 / 30

        #define the default rules (make input work and other stuff)
        @self.engine_event_rules.rule(pygame.KEYDOWN)
        def handleInput(event):
            try: self.keyboard_bindings[event.key](True)
            except KeyError: print 'Key',chr(event.key)if event.key<256 else'','not bound'
            
        @self.engine_event_rules.rule(pygame.KEYUP)
        def handleInput(event):
            try: self.keyboard_bindings[event.key](False)
            except KeyError: pass
            
        @self.engine_event_rules.rule(pygame.MOUSEBUTTONDOWN)
        def handleMouseDown(event):
            try: self.mouse_bindings[event.button](True,event.pos)
            except KeyError: pass

        @self.engine_event_rules.rule(pygame.MOUSEBUTTONUP)
        def handleMouseUp(event):
            try: self.mouse_bindings[event.button](False,event.pos)
            except KeyError: pass

        '''@gamerules.rule(pygame.MOUSEMOTION)
        def handleMouseMove(event):
            gamerules.mbindings[4](event.pos) #TODO: fix mouse button binding'''



        # CommandGiver provides an interface to the Commandable class,
        # and raising an event when a command is given
        @self.engine_event_rules.eventMaker
        def commandGiver(start,player,command):
            player.entity.giveCommand(command, start)
        self.commandGiver = commandGiver


    def init(self):
        
        pygame.init()
        
        self.clock = pygame.time.Clock()
        self.input = Input()
        self.renderer = Renderer()
        self.renderer.init(self.windowWidth, self.windowHeight, self.fullscreen, self.forceResolution)
        # TODO: Check what self.clock is and how it relates to TimeMgr
        self.timeMgr = TimeMgr()
        self.timeMgr.init()

        self.world = World(self)

        self.physics_world = PhysicsWorld(self.engine_event_rules)

        self.mouseJoint = None
        self.gui = GUI(self)
        
        self.running = False

        # TODO: Replace this with entity\event\etc. manager
        self.entities = {}
        
        self.time_accel = 0.0

    
    def run(self):
        tickDuration = 1.0 / float(self.tickRate)
        self.running = True
        tickCount = 0
        frameCount = 0
        lastSecond = time.clock()
        lastIterationTime = time.clock()
        while self.running:
            
            # Sweet main loop that ticks at self.tickRate and renders as
            # fast as possible, with no wasted time. And so simple!
            # The only limitation is that at frame rate > tick rate, the
            # extra frames will not make any visual difference unless we
            # add interpolation before rendering. But our tick rate must
            # be 60 anyway, and that's more than high enough.
            
            tickDuration = 1.0 / float(self.tickRate) * (1.0 - self.time_accel)
            
            now = time.clock()
            while lastIterationTime + tickDuration <= now:
                self.tick()
                tickCount += 1
                lastIterationTime += tickDuration
            self.render()
            frameCount += 1
            
            # Print the tick and frame rates averaged over the last second
            elapsedSinceLastSecond = time.clock() - lastSecond
            if elapsedSinceLastSecond > 1.0:
                print "Ticks/second: ", tickCount / elapsedSinceLastSecond
                print "Frames/second: ", frameCount / elapsedSinceLastSecond
                tickCount = 0
                frameCount = 0
                lastSecond = time.clock()
            
    def tick(self):
        
        self.input.update()
        self.handleOSEvents()
        # Perform physics updates
        self.physics_world.step(1.0 / self.tickRate)

        self.world.update(1.0 / self.tickRate)
        
    def render(self):
        self.renderer.camera.update(1.0)
        self.renderer.render()



    # TODO: Replace x and y with a proper vector type
    def queryEntitiesAtPoint(self, x, y):
        """Returns a list of entities that contain physical shapes that
           intersect the given point.
        """
        min_size = b2Vec2(0.001, 0.001)
        pos1 = b2Vec2(x, y) - min_size
        pos2 = b2Vec2(x, y) + min_size

        return self.physics_world.queryRect(pos1,pos2)
        

    def queryEntitiesInRect(self, x1, y1, x2, y2):
        """Returns a list of entities that contain physical shapes that
           intersect the given rectangle.
        """
        # Make a small box

        min_size =0.001
        # Check point order
        if x2 < x1:
            x1, x2 = x2, x1
        if y2 < y1:
            y1, y2 = y2, y1
        # Check minimum rect size
        if x2-x1 < 2*min_size.x:
            x1 -= min_size.x
            x2 += min_size.x
        if y2-y1 < 2*min_size.y:
            y1 -= min_size.y
            y2 += min_size.y

        return self.physics_world.queryRect(b2Vec(x1,y1),b2Vec(x2,y2))


    def handleOSEvents(self):
        for event in pygame.event.get():
            self.engine_event_rules.raiseEvent(event.type,event=event)



    def convertPxlsToGUICoords(self, point):
        point = list(point)
        # First, scale out pixels
        size_pxls = self.renderer.getSizeInPxls()
        point[0] /= float(size_pxls[0])
        point[1] /= -float(size_pxls[1])
        # Then place the origin at the centre of the window and
        #  scale to [-1, 1] [-aspect, aspect]
        size = self.renderer.getSize()
        point[0] = (point[0] - 0.5) * size[0]
        point[1] = (point[1] + 0.5) * size[1]
        return point
    def convertPxlsToWorldCoords(self, point):
        # First convert to GUI coords
        pos = self.convertPxlsToGUICoords(point)

        # Then undo the camera transformations
        angle = math.radians(-self.renderer.camera.getAngle())
        tmp_pos = list(pos)
        tmp_pos[0] = pos[0]*math.cos(angle) - pos[1]*math.sin(angle)
        tmp_pos[1] = pos[0]*math.sin(angle) + pos[1]*math.cos(angle)
        pos = tmp_pos
        
        pos[0] /= self.renderer.camera.getZoom()
        pos[1] /= self.renderer.camera.getZoom()
        pos[0] += self.renderer.camera.getPos()[0]
        pos[1] += self.renderer.camera.getPos()[1]
        
        return pos

    # TODO: Remove this as it seems the above functions are used instead
    def convertWindowCoordsToWorld(self, pos):
        """
        TODO: Rewrite to use vectors and matrices
        """
        pos = list(pos)
        # First, scale out pixels
        pos[0] /= float(self.renderer.width)
        pos[1] /= -float(self.renderer.height)
        # Then place the origin at the centre of the window and scale to [-1, 1]
        pos[0] = (pos[0] - 0.5) * 2.0
        pos[1] = (pos[1] + 0.5) * 2.0 * self.renderer.aspect
        
        # Finally, account for the camera (transformations)
        pos[0] /= self.renderer.camera.getZoom()
        pos[1] /= self.renderer.camera.getZoom()
        pos[0] += self.renderer.camera.getPos()[0]
        pos[1] += self.renderer.camera.getPos()[1]

        angle = math.radians(-self.renderer.camera.getAngle())
        tmp_pos = list(pos)
        tmp_pos[0] = pos[0]*math.cos(angle) - pos[1]*math.sin(angle)
        tmp_pos[1] = pos[0]*math.sin(angle) + pos[1]*math.cos(angle)
        
        return tmp_pos

    def playerCommand(self,player,command):
        return curry(self.commandGiver,player,command)

class CameraController(Entity):
    def __init__(self, engine, name):
        Entity.__init__(self, engine, name)
    def update(self, timeStep):
        # ...
        pass


# TODO: really belongs in utils or something...
def curry(func,*args,**kwargs):
    def curried(*moreargs,**morekwargs):
        morekwargs.update(kwargs)
        return func(*(moreargs+args),**morekwargs)
    return curried

class Player(object):
    def __init__(self,name):
        self.name = name
        self.entity = None





